feat: env: map in mantle.yaml for workflow expression variables (#74)#119
Conversation
Add Env map[string]string field to Config struct, parsed from the env: section in mantle.yaml. Keys are normalized to uppercase to match the MANTLE_ENV_* convention used by the CEL evaluator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename envVars() to mergeEnvVars(configEnv) which starts with config values from mantle.yaml env: section, then overlays MANTLE_ENV_* OS environment variables. When a key exists in both sources, the OS env var wins and an info log is emitted. Add SetConfigEnv setter to avoid changing the NewEvaluator signature. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Call eng.CEL.SetConfigEnv(cfg.Env) after engine.New(database) in the run and serve CLI commands so that env: values from mantle.yaml are available in CEL workflow expressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Workflow Expression Variables section to configuration docs covering the env: YAML syntax, CEL expression usage, and MANTLE_ENV_* override precedence with info logging on conflicts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 31 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds an optional Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as "CLI (run/serve)"
participant Config as "Config Loader\n(mantle.yaml)"
participant Engine as "Engine"
participant Evaluator as "CEL Evaluator"
participant OS as "OS Env (MANTLE_ENV_*)"
participant Workflow as "Workflow Runner"
CLI->>Config: load config (includes env map)
CLI->>Engine: engine.New(database)
Engine->>Evaluator: eng.CEL.SetConfigEnv(cfg.Env)
Evaluator->>OS: read MANTLE_ENV_* variables
Evaluator->>Evaluator: mergeEnvVars(configEnv, OS) -- OS wins on conflicts
CLI->>Workflow: execute workflow (uses env.<KEY> in CEL)
Workflow->>Evaluator: evaluate expressions referencing env.<KEY>
Evaluator-->>Workflow: evaluated values
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Deploying mantle with
|
| Latest commit: |
3fbecb7
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://053723c4.mantle-cxy.pages.dev |
| Branch Preview URL: | https://feature-env-map-config.mantle-cxy.pages.dev |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/engine/internal/config/config_test.go (1)
313-333: Consider adding a test case for case normalization.The test uses uppercase keys in the YAML (
APP_NAME,REGION,DEBUG), which matches the expected output. However, sinceconfig.gonormalizes keys to uppercase viastrings.ToUpper, consider adding a test case with lowercase/mixed-case YAML keys to verify the normalization logic works correctly.📋 Example additional test case
+func TestLoad_EnvMapCaseNormalization(t *testing.T) { + dir := t.TempDir() + configFile := filepath.Join(dir, "mantle.yaml") + err := os.WriteFile(configFile, []byte(` +env: + app_name: "my-app" + Region: "us-east-1" +`), 0644) + require.NoError(t, err) + + cmd := newTestCommand() + _ = cmd.Flags().Set("config", configFile) + + cfg, err := Load(cmd) + require.NoError(t, err) + + // Keys should be normalized to uppercase + assert.Equal(t, "my-app", cfg.Env["APP_NAME"]) + assert.Equal(t, "us-east-1", cfg.Env["REGION"]) +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/engine/internal/config/config_test.go` around lines 313 - 333, The test TestLoad_EnvMap currently only asserts behavior with already-uppercase YAML keys; add an additional subcase that writes env keys in lowercase and mixed-case (e.g., "app_name", "Region", "Debug") to the temporary mantle.yaml, call Load(cmd) the same way, and assert that cfg.Env contains the normalized uppercase keys ("APP_NAME", "REGION", "DEBUG") with the expected values to verify the strings.ToUpper normalization in Load and the cfg.Env map behavior.packages/engine/internal/cel/cel.go (1)
211-217: Document or synchronizeenvCacheaccess to prevent future concurrency issues.
SetConfigEnvwrites toe.envCachewithout synchronization whileEvalreads from it. Current usage is safe—SetConfigEnv is called once during engine initialization (serve.go:52, run.go:67) before evaluations begin. However, to prevent data races if the API is used differently in the future, consider adding a sync.RWMutex or a comment documenting the initialization-only requirement.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/engine/internal/cel/cel.go` around lines 211 - 217, SetConfigEnv writes to e.envCache without synchronization while Eval reads it, risking future data races; add a sync.RWMutex field (e.g., envMu) to the Evaluator struct and use envMu.Lock()/Unlock() in SetConfigEnv and envMu.RLock()/RUnlock() in Eval (and any other readers) when accessing e.envCache, or if you prefer the lightweight option, add a clear comment on the Evaluator type stating envCache is initialization-only and must not be mutated after startup; reference SetConfigEnv, Eval, Evaluator, envCache, and mergeEnvVars when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/site/src/content/docs/configuration.md`:
- Around line 134-136: Update the fenced code block that contains the log line
"INFO env variable overrides config key=MANTLE_ENV_REGION config_key=env.REGION"
to include a language specifier (e.g., use ```text or ```log) so the snippet
renders with proper syntax highlighting; locate the fenced block in the docs
content around that exact log string and add the language token immediately
after the opening backticks.
---
Nitpick comments:
In `@packages/engine/internal/cel/cel.go`:
- Around line 211-217: SetConfigEnv writes to e.envCache without synchronization
while Eval reads it, risking future data races; add a sync.RWMutex field (e.g.,
envMu) to the Evaluator struct and use envMu.Lock()/Unlock() in SetConfigEnv and
envMu.RLock()/RUnlock() in Eval (and any other readers) when accessing
e.envCache, or if you prefer the lightweight option, add a clear comment on the
Evaluator type stating envCache is initialization-only and must not be mutated
after startup; reference SetConfigEnv, Eval, Evaluator, envCache, and
mergeEnvVars when making the change.
In `@packages/engine/internal/config/config_test.go`:
- Around line 313-333: The test TestLoad_EnvMap currently only asserts behavior
with already-uppercase YAML keys; add an additional subcase that writes env keys
in lowercase and mixed-case (e.g., "app_name", "Region", "Debug") to the
temporary mantle.yaml, call Load(cmd) the same way, and assert that cfg.Env
contains the normalized uppercase keys ("APP_NAME", "REGION", "DEBUG") with the
expected values to verify the strings.ToUpper normalization in Load and the
cfg.Env map behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 777859c4-9802-4919-b3bd-2f9a9ceb0516
📒 Files selected for processing (7)
packages/engine/internal/cel/cel.gopackages/engine/internal/cel/cel_test.gopackages/engine/internal/cli/run.gopackages/engine/internal/cli/serve.gopackages/engine/internal/config/config.gopackages/engine/internal/config/config_test.gopackages/site/src/content/docs/configuration.md
…block - Merge main (v0.4.0) into feature branch - Keep Storage (renamed from Tmp) + add Env field - Keep concurrency config + add SetConfigEnv wiring - Keep storage docs + add env.* row and expression variables section - Add text language specifier to log code block (PR feedback)
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
packages/engine/internal/cli/run.go (1)
67-68: Minor ordering inconsistency withserve.go.In
serve.go(lines 51-54),SetConfigEnvis called immediately afterengine.New, before settingMaxConcurrentExecutionsPerTeam. Here the order is reversed. While functionally equivalent, aligning the order improves consistency across CLI commands.🔧 Suggested reordering for consistency
eng, err := engine.New(database) if err != nil { return fmt.Errorf("creating engine: %w", err) } + eng.CEL.SetConfigEnv(cfg.Env) eng.MaxConcurrentExecutionsPerTeam = cfg.Engine.MaxConcurrentExecutionsPerTeam - eng.CEL.SetConfigEnv(cfg.Env)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/engine/internal/cli/run.go` around lines 67 - 68, Reorder the two assignments so they match serve.go: after creating the engine via engine.New, call eng.CEL.SetConfigEnv(cfg.Env) immediately, then set eng.MaxConcurrentExecutionsPerTeam = cfg.Engine.MaxConcurrentExecutionsPerTeam; adjust the sequence around the eng variable (engine.New, eng.CEL.SetConfigEnv, then setting MaxConcurrentExecutionsPerTeam) to keep CLI command initialization consistent.packages/engine/internal/cel/cel.go (1)
227-233: Consider thread-safety forSetConfigEnvif called concurrently.
envCacheis read inEval(line 88) without synchronization whileSetConfigEnvwrites to it. Current usage (init-time only in CLI commands) is safe, but ifSetConfigEnvis ever called during concurrent execution, this becomes a data race.If dynamic reconfiguration is planned, consider adding a
sync.RWMutexto protectenvCache. Otherwise, documenting the "call only during initialization" constraint would be sufficient.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/engine/internal/cel/cel.go` around lines 227 - 233, The SetConfigEnv method writes to Evaluator.envCache while Eval reads it without synchronization, creating a potential data race; add a sync.RWMutex field (e.g., envMu) to the Evaluator struct and use envMu.Lock()/Unlock() in SetConfigEnv when assigning e.configEnv and e.envCache (and when calling mergeEnvVars), and use envMu.RLock()/RUnlock() in Eval where it reads e.envCache to protect concurrent access; if you prefer not to change runtime behavior, instead add a clear comment on Evaluator.SetConfigEnv and Eval stating that SetConfigEnv must only be called during initialization.packages/engine/internal/cel/cel_test.go (1)
300-304: Prefert.Setenvover manualos.Setenv/defer os.Unsetenv.Using
t.Setenvprovides automatic cleanup and is parallel-safe. The manual approach works but is more error-prone.♻️ Suggested refactor
func TestEnvVars_PrefixStripping(t *testing.T) { // Directly test the mergeEnvVars function. - os.Setenv("MANTLE_ENV_TEST_KEY", "test-value") - defer os.Unsetenv("MANTLE_ENV_TEST_KEY") - - os.Setenv("MANTLE_DATABASE_URL", "should-not-appear") - defer os.Unsetenv("MANTLE_DATABASE_URL") + t.Setenv("MANTLE_ENV_TEST_KEY", "test-value") + t.Setenv("MANTLE_DATABASE_URL", "should-not-appear") result := mergeEnvVars(nil)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/engine/internal/cel/cel_test.go` around lines 300 - 304, In the test in cel_test.go that currently calls os.Setenv("MANTLE_ENV_TEST_KEY", ...) and os.Setenv("MANTLE_DATABASE_URL", ...) with deferred os.Unsetenv calls, replace those manual environment manipulations with t.Setenv("MANTLE_ENV_TEST_KEY", "test-value") and t.Setenv("MANTLE_DATABASE_URL", "should-not-appear") respectively and remove the corresponding defer os.Unsetenv lines so the testing.T-managed cleanup (and parallel-safety) is used instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/site/src/content/docs/configuration.md`:
- Around line 86-87: The docs and env table disagree on the default for
engine.max_concurrent_executions_per_team: the configuration docs correctly
state default 0 (unlimited) and config.go has no SetDefault so Go zero value is
0, but the Environment Variables table still shows 10; update the Environment
Variables table entry for engine.max_concurrent_executions_per_team to 0 (or
mark as unlimited) to match config.go and the configuration docs, and verify
references to engine.max_concurrent_executions_per_team across docs to ensure
consistency.
---
Nitpick comments:
In `@packages/engine/internal/cel/cel_test.go`:
- Around line 300-304: In the test in cel_test.go that currently calls
os.Setenv("MANTLE_ENV_TEST_KEY", ...) and os.Setenv("MANTLE_DATABASE_URL", ...)
with deferred os.Unsetenv calls, replace those manual environment manipulations
with t.Setenv("MANTLE_ENV_TEST_KEY", "test-value") and
t.Setenv("MANTLE_DATABASE_URL", "should-not-appear") respectively and remove the
corresponding defer os.Unsetenv lines so the testing.T-managed cleanup (and
parallel-safety) is used instead.
In `@packages/engine/internal/cel/cel.go`:
- Around line 227-233: The SetConfigEnv method writes to Evaluator.envCache
while Eval reads it without synchronization, creating a potential data race; add
a sync.RWMutex field (e.g., envMu) to the Evaluator struct and use
envMu.Lock()/Unlock() in SetConfigEnv when assigning e.configEnv and e.envCache
(and when calling mergeEnvVars), and use envMu.RLock()/RUnlock() in Eval where
it reads e.envCache to protect concurrent access; if you prefer not to change
runtime behavior, instead add a clear comment on Evaluator.SetConfigEnv and Eval
stating that SetConfigEnv must only be called during initialization.
In `@packages/engine/internal/cli/run.go`:
- Around line 67-68: Reorder the two assignments so they match serve.go: after
creating the engine via engine.New, call eng.CEL.SetConfigEnv(cfg.Env)
immediately, then set eng.MaxConcurrentExecutionsPerTeam =
cfg.Engine.MaxConcurrentExecutionsPerTeam; adjust the sequence around the eng
variable (engine.New, eng.CEL.SetConfigEnv, then setting
MaxConcurrentExecutionsPerTeam) to keep CLI command initialization consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8b6a3ec9-a9a8-47ae-ab52-9882ed48a8bb
📒 Files selected for processing (7)
packages/engine/internal/cel/cel.gopackages/engine/internal/cel/cel_test.gopackages/engine/internal/cli/run.gopackages/engine/internal/cli/serve.gopackages/engine/internal/config/config.gopackages/engine/internal/config/config_test.gopackages/site/src/content/docs/configuration.md
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/engine/internal/cli/serve.go
- packages/engine/internal/config/config_test.go
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/site/src/content/docs/configuration.md`:
- Around line 104-162: Update the docs to explicitly state that env keys are
case-sensitive and must be uppercase when using MANTLE_ENV_* overrides: explain
that config keys are normalized to uppercase (e.g., env keys like APP_NAME,
REGION) and that OS vars after stripping the MANTLE_ENV_ prefix must also be
provided in uppercase (MANTLE_ENV_REGION) or they will not match the normalized
keys in the env map (causing duplicate keys like REGION and region), and
reference the env:<KEY> usage (env.APP_NAME) and the MANTLE_ENV_* prefix so
readers know exactly which names to use.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9abc674a-3191-44ff-9805-a3eb0a1a880a
📒 Files selected for processing (1)
packages/site/src/content/docs/configuration.md
Summary
Adds an optional
env:section tomantle.yamlthat populates theenv.*namespace in CEL expressions, merged withMANTLE_ENV_*shell environment variables at runtime.env:values in YAML serve as defaultsMANTLE_ENV_*environment variables take precedence (override YAML values)slog.Infomessage is emitted when an env var overrides a config valueMANTLE_ENV_*conventionCloses #74
Example
Test plan
env:section parses intomap[string]stringenv:section results in nil map (no error)env.KEYMANTLE_ENV_*overrides config valueSetConfigEnv(nil)does not panic🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests